package jmt.engine.NetStrategies.QueueGetStrategies;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import jmt.common.exception.NetException;
import jmt.engine.NetStrategies.QueueGetStrategy;
import jmt.engine.QueueNet.Job;
import jmt.engine.QueueNet.JobClassList;
import jmt.engine.QueueNet.JobInfo;
import jmt.engine.QueueNet.JobInfoList;
import jmt.engine.QueueNet.NetNode;
import jmt.engine.QueueNet.NetSystem;
import jmt.engine.QueueNet.NodeList;
import jmt.engine.QueueNet.NodeSection;
import jmt.framework.data.MacroReplacer;

/**
 * 
 * @author Riccardo Pinciroli
 *
 */

public class EnergyAwareGettingStrategy implements QueueGetStrategy {

	private int numSubSystems, winSize, age;
	private boolean toDrop;
	private HashMap<Integer, Double> optimalBetas;
	private HashMap<Integer, Integer> numMaxJobs;
	
	private String dirName = null; //Directory where files with EnergyEfficiency measures will be put
	
	private String nodeParentName = null;
	
	private static final int rowOrder = 100; //Used to linearize the optimal betas matrix
	private static final int neededFreeSS = 1; //It must be changed if you want to try some other combination
	
	public EnergyAwareGettingStrategy(int numSubSystems, int winSize, int age, boolean toDrop, HashMap<Integer, Double> betaValues, HashMap<Integer, Integer> numMaxJobs, String optimizerName) throws IOException {
		this.numSubSystems = numSubSystems;
		this.winSize = winSize;
		this.age = age;
		this.toDrop = toDrop;
		this.optimalBetas = betaValues;
		this.numMaxJobs = numMaxJobs;
		this.nodeParentName = optimizerName;
		/*createFolderAndFilesToContainEnergyEfficiencyMeasure();*/
	}
	
	public Job get(NodeSection callingSection, JobInfoList Queue) throws jmt.common.exception.NetException {
		//System.out.println("Queue "+Queue.toString());
		List<JobInfo> cuttedQueue = cutQueue(Queue);
//		System.out.println("EAGS(44) -> "+cuttedQueue.size()+"/"+Queue.size()+" job in coda");
//		for(int i=0; i<cuttedQueue.size(); i++) {
//			System.out.println("EAGS(46) -> Classe del "+(i+1)+"° job in coda: "+(cuttedQueue.get(i).getJob().getJobClass().getId()+1));
//		}
		NodeList CPUs = callingSection.getOwnerNode().getOutputNodes();
		//0) First of all it writes files needed for energy efficiency measure
		/*writeFilesForEnergyEfficiencyMeasures(CPUs);*/
		HashMap<Integer, Boolean> freeSpaceInSS = thereIsSpaceInSS(CPUs);
		//1) Checks for job too old and sends what has been found to the next section
		JobInfo jobTooOld = jobTooOld(cuttedQueue);
		if(jobTooOld != null) {
			for(int i=0; i<numSubSystems; i++) {
				if(freeSpaceInSS.get(i)) {
//					System.out.println("EAGS(55) -> Inviato job di classe "+(jobTooOld.getJob().getJobClass().getId()+1)+" perché troppo vecchio");
					Queue.remove(jobTooOld);
					return jobTooOld.getJob();
				}
			}
//			System.out.println("EAGS(60) -> Non c'è più spazio libero nei subsystems, quindi ho droppato il job di classe "+(jobTooOld.getJob().getJobClass().getId()+1)+" perché troppo vecchio");
//			System.out.println("----------------------------------");
			if(toDrop) {
				return jobTooOld.getJob();
			}
			return null;
		}
		
		//2) If there is not jobs too old, it checks what to send to the next section
		//2.1) If there is only a job in the queue and there is space in (at least) a SS, then the job is forwarded
		if(cuttedQueue.size() == 1) {
			for(int i=0; i<numSubSystems; i++) {
				if(freeSpaceInSS.get(i)) { //It's enough that just one SS has still free space
//					System.out.println("EAGS(71) -> C'è solo un job di classe "+(cuttedQueue.get(0).getJob().getJobClass().getId()+1)+" in coda e almeno un subsystem libero. Invio il job");
					Job jobToForward = Queue.removeFirst().getJob();
					return jobToForward;
				}
			}
//			System.out.println("EAGS(76) -> Non c'è spazio nei subsystem. Non invio");
//			System.out.println("----------------------------------");
			return null;
		}
		//2.2) If there is more than one job in the queue and there is space in (at least) "neededFreeSS" SS, then a job chosen based on betas is forwarded
		else if(cuttedQueue.size() > 1) {
			int freeSS = 0;
			for(int i=0; i<numSubSystems; i++) {
				if(freeSpaceInSS.get(i)) {
					freeSS++; //In this way it counts how many free SS there are in the energy optimizer
				}
			}
			if(freeSS >= neededFreeSS) {
				HashMap<Integer, Double> actualBetas = getNextHopActualBetas(CPUs);
				int numClasses = CPUs.get(0).getJobClasses().size(); //CPUs.get(0) because every CPU is good to find the number of classes
				HashMap<Integer, Double> residualBetas = new HashMap<Integer, Double>();
				for(int i=0; i<numSubSystems; i++) {
					for(int j=0; j<numClasses; j++) {
//						System.out.println("EAGS(94) -> |"+optimalBetas.get(i*rowOrder+j)+" - "+actualBetas.get(i*rowOrder+j)+"| = "+Math.abs(optimalBetas.get(i*rowOrder+j) - actualBetas.get(i*rowOrder+j)));
						residualBetas.put(i*rowOrder+j, Math.abs(optimalBetas.get(i*rowOrder+j) - actualBetas.get(i*rowOrder+j)));
					}
				}
				int minClassKey = getMinimumBetaID(residualBetas, freeSpaceInSS)%rowOrder; //Here, there is the classKey of the job to send
				JobInfo jobToSendInfo = null;
				//Looks in the cuttedQueue to find a job of the searched class
				for(int i=0; i<cuttedQueue.size(); i++) {
					JobInfo jobInfo = cuttedQueue.get(i);
//					System.out.println("EAGS(103) -> Dovrebbe essere inviato un job di classe "+(minClassKey+1));
					if(jobInfo.getJob().getJobClass().getId() == minClassKey) {
						jobToSendInfo = jobInfo;
						break; //At the first good job in the queue it stops to look for another job
					}
//					System.out.println("EAGS(108) -> Proviamo con il prossimo job in coda");
				}
				//If no jobs of the specified class are found in the cut queue, it sends the first job it has in the queue
				if(jobToSendInfo == null) {
					jobToSendInfo = Queue.removeFirst();
//					System.out.println("EAGS(113) -> Invio il primo job in coda che è di classe "+(jobToSendInfo.getJob().getJobClass().getId()+1));
					return jobToSendInfo.getJob();
//					return null;
				} else {
//					System.out.println("EAGS(117) -> Inoltrato job di classe "+(jobToSendInfo.getJob().getJobClass().getId()+1));
					Queue.removeFirst(jobToSendInfo.getJob().getJobClass()); //The first job in queue is the older one!
					return jobToSendInfo.getJob();
				}
			} else {
//				System.out.println("----------------------------------");
				return null;
			}
		}
		// 2.3) There aren't jobs in the queue
		else {
//			System.out.println("EAGS(128) -> The queue is empty");
			return null;
		}
	}
		
	
	public boolean check() {
		return true;
	}
	
	private List<JobInfo> cutQueue(JobInfoList Queue) {
		List<JobInfo> cuttedQueue = new ArrayList<JobInfo>();
		List<JobInfo> queueToBeCut = Queue.getJobList();
		//It uses the minimum between winSize and the original queue length
		if(winSize < queueToBeCut.size()) {
			for(int i=0; i<winSize; i++) {
				cuttedQueue.add(queueToBeCut.get(i));
			}
		} else {
			cuttedQueue = queueToBeCut;
		}
		return cuttedQueue;
	}
	
	private JobInfo jobTooOld(List<JobInfo> cuttedQueue) {
		JobInfo jobInfo = cuttedQueue.get(0); //The oldest job is the first one!
		//double ageOfThisJob = NetSystem.getTime() - jobInfo.getJob().getBornTime();
		double ageOfThisJob = NetSystem.getTime() - jobInfo.getJob().getSystemEnteringTime();
//		System.out.println("EAGS(155) -> Età del 1° job in coda: "+ageOfThisJob);
		if(ageOfThisJob > this.age) {
			return jobInfo;
		} else {
			return null;
		}
//		for(int i=0; i<cuttedQueue.size(); i++) {
//			JobInfo jobInfo = cuttedQueue.get(i);
//			double ageOfThisJob = NetSystem.getTime() - jobInfo.getJob().getBornTime();
//		System.out.println("EAGS(115) -> Età del "+(i+1)+"° job in coda: "+ageOfThisJob);
//			if(ageOfThisJob > this.age) {
//				return jobInfo;
//			}
//		}
//		return null;
	}
	
	private HashMap<Integer, Double> getNextHopActualBetas(NodeList CPUs) throws jmt.common.exception.NetException {
		HashMap<Integer, Double> actualBetas = new HashMap<Integer, Double>();
		for(int i=0; i<numSubSystems; i++) {
			NetNode CPU = CPUs.get(i);
			NodeList IOs = CPU.getOutputNodes();
			double totJobsInSS = CPU.getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS); //Here, I have only jobs in CPU
			for(int j=0; j<IOs.size(); j++) {
				totJobsInSS += IOs.get(j).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS); //At the end of the FOR, I have all the jobs in the subsystem-- è la IO.size(), non la classe!!!!!!!
			}
			totJobsInSS += 1; //This is the job that router will send in the next hop
			
			double totJobsInSSPerClass = 0;
			JobClassList classes = CPU.getJobClasses();
			for(int j=0; j<classes.size(); j++) {
				totJobsInSSPerClass = CPU.getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS, classes.get(j));
				for(int k=0; k<IOs.size(); k++) {
					totJobsInSSPerClass += IOs.get(k).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS, classes.get(j));
				}
				totJobsInSSPerClass += 1; //This is the job that router will send in the next hop
				actualBetas.put(i*rowOrder+j, totJobsInSSPerClass/totJobsInSS);
			}
		}
		return actualBetas;
	}
	
	private Integer getMinimumBetaID(HashMap<Integer, Double> residualBetas, HashMap<Integer, Boolean> freeSpaceInSS) {
		boolean flag = true; //With this var, I can iterate on freeSpaceInSS (1 vale for SS) every two times I iterate on residualBetas (2 values for SS (1 value for each class))
		double min = 2.0; //Random value that betas can't be equal to
		Integer minClassKey = null;
		Iterator it1 = residualBetas.entrySet().iterator();
		Iterator it2 = freeSpaceInSS.entrySet().iterator();
		Map.Entry pairs2 = null;
		while(it1.hasNext()) {
			Map.Entry pairs1 = (Map.Entry)it1.next();
			if(flag) {
				pairs2 = (Map.Entry)it2.next();
			}
			flag = !flag;
			Double residualBeta = (Double) pairs1.getValue();
//			System.out.println("EAGS(211) -> min = "+min+" --- beta = "+residualBeta+" --- class = "+((int)pairs1.getKey()+1));
			if(residualBeta < min && Boolean.TRUE.equals(pairs2.getValue())) {
				min = residualBeta;
				minClassKey = (Integer) pairs1.getKey();
			}
		}
		return minClassKey;
	}
	
	public HashMap<Integer, Boolean> thereIsSpaceInSS(NodeList CPUs) throws jmt.common.exception.NetException {
		HashMap<Integer, Boolean> freeSpaceInSS = new HashMap<Integer, Boolean>();
		for(int i=0; i<numSubSystems; i++) {
			NetNode CPU = CPUs.get(i);
			NodeList IOs = CPU.getOutputNodes();
			int totJobsInSS = CPU.getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS); //Here, I have only jobs in CPU
			for(int j=0; j<IOs.size(); j++) {
				totJobsInSS += IOs.get(j).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS); //At the end of the FOR, I have all the jobs in the subsystem
			}
			if(totJobsInSS < numMaxJobs.get(i)) {
				freeSpaceInSS.put(i, true);
			} else {
				freeSpaceInSS.put(i, false);
			}
		}
		return freeSpaceInSS;
	}
	
	//The following methods are used for the energy efficiency measure
	private HashMap<Integer, Double> getThisHopActualBetas(NodeList CPUs) throws jmt.common.exception.NetException {
		HashMap<Integer, Double> actualBetas = new HashMap<Integer, Double>();
		for(int i=0; i<numSubSystems; i++) {
			NetNode CPU = CPUs.get(i);
			NodeList IOs = CPU.getOutputNodes();
			double totJobsInSS = CPU.getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS); //Here, I have only jobs in CPU
			for(int j=0; j<IOs.size(); j++) {
				totJobsInSS += IOs.get(j).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS); //At the end of the FOR, I have all the jobs in the subsystem-- è la IO.size(), non la classe!!!!!!!
			}
			
			double totJobsInSSPerClass = 0;
			JobClassList classes = CPU.getJobClasses();
			for(int j=0; j<classes.size(); j++) {
				totJobsInSSPerClass = CPU.getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS, classes.get(j));
				for(int k=0; k<IOs.size(); k++) {
					totJobsInSSPerClass += IOs.get(k).getIntNodeProperty(NetNode.PROPERTY_ID_RESIDENT_JOBS, classes.get(j));
				}
				if(totJobsInSS != 0) {
					actualBetas.put(i*rowOrder+j, totJobsInSSPerClass/totJobsInSS);
				} else {
					actualBetas.put(i*rowOrder+j, totJobsInSS);
				}
			}
		}
		return actualBetas;
	}
	
	private HashMap<Integer, Double> getThisHopResidualBetas(NodeList CPUs) throws jmt.common.exception.NetException {
		HashMap<Integer, Double> residualBetas = new HashMap<Integer, Double>();
		HashMap<Integer, Double> actualBetas = getThisHopActualBetas(CPUs);
		int numClasses = CPUs.get(0).getJobClasses().size();
		for(int i=0; i<numSubSystems; i++) {
			for(int j=0; j<numClasses; j++) {
				residualBetas.put(i*rowOrder+j, Math.abs(optimalBetas.get(i*rowOrder+j) - actualBetas.get(i*rowOrder+j)));
			}
		}
		return residualBetas;
	}
	
	/*private void createFolderAndFilesToContainEnergyEfficiencyMeasure() throws IOException {
		String timeLog = new SimpleDateFormat("yyyyMMdd_HHmmss").format(Calendar.getInstance().getTime());
		String dirPath = MacroReplacer.replace(MacroReplacer.MACRO_WORKDIR);
		dirName = dirPath+"EnergyEfficiencyMeasures_"+timeLog;
		File dir = new File(dirName);
		dir.mkdir();
		Iterator it = optimalBetas.entrySet().iterator();
		while(it.hasNext()) {
			Map.Entry pairs = (Map.Entry)it.next();
			int key = (Integer) pairs.getKey();
			int ssKey = key/rowOrder;
			int classKey = key%rowOrder;
			String resFileName = getResFileName(ssKey, classKey);
			new File(dir, resFileName).createNewFile();
		}
		//Only a file for time, because it is equal for each subsystem and for each class <- Ne sei così sicuro? Rivedere questa parte qualora dovessero servire questi file!!
		String timeFileName = getTimeFileName();
		new File(dir, timeFileName).createNewFile();
	}
	
	private void writeFilesForEnergyEfficiencyMeasures(NodeList CPUs) throws jmt.common.exception.NetException {
		HashMap<Integer, Double> residualBetas = getThisHopResidualBetas(CPUs);
		Iterator it = residualBetas.entrySet().iterator();
		while(it.hasNext()) {
			Map.Entry pairs = (Map.Entry)it.next();
			int key = (Integer) pairs.getKey();
			Double value = (Double) pairs.getValue();
			int ssKey = key/rowOrder;
			int classKey = key%rowOrder;
			String resFileName = getResFileName(ssKey, classKey);	
			writeFile(resFileName, String.valueOf(value));
		}
		//Only a file for time, because it is equal for each subsystem and for each class <- Ne sei così sicuro? Rivedere questa parte qualora dovessero servire questi file!!
		String timeFileName = getTimeFileName();
		writeFile(timeFileName, String.valueOf(NetSystem.getTime()));
		
	}
	
	private String getResFileName(int ssKey, int classKey) {
		return "res_SS"+(ssKey+1)+"_cl"+(classKey+1)+"_"+nodeParentName+".txt";
	}
	
	private String getTimeFileName() {
		return "simulationTime_"+nodeParentName+".txt";
	}
	
	private void writeFile(String fileName, String valueToWrite) {
		try {
			FileWriter writer = new FileWriter(dirName+"/"+fileName, true);
			writer.append(String.valueOf(valueToWrite)+" ");
			writer.flush();
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}*/
	
	public boolean thereIsNotFreeSubSystem(NodeList CPUs) {
		try {
			HashMap<Integer, Boolean> freeSS = this.thereIsSpaceInSS(CPUs);
			for(int i=0; i<freeSS.size(); i++) {
				if(freeSS.get(i)) {
					return false; //There is at least a free SS
				}
			}
			return true; //There is not free SS
		} catch (NetException e) {
			e.printStackTrace();
		}
		return (Boolean) null;
	}
	
}
